package com.exception;

/*
##异常的传播
    当某个方法抛出了异常时，如果当前方法没有捕获异常，异常就会被抛到上层调用方法，直到遇到某个try ... catch被捕获为止
    通过printStackTrace()可以打印出方法的调用栈，
    printStackTrace()对于调试错误非常有用，上述信息表示：NumberFormatException是在java.lang.Integer.parseInt方法中被抛出的，从下往上看，调用层次依次是：
        main()调用process1()；
        process1()调用process2()；
        process2()调用Integer.parseInt(String)；
        Integer.parseInt(String)调用Integer.parseInt(String, int)。
        查看Integer.java源码可知，抛出异常的方法代码
        并且，每层调用均给出了源代码的行号，可直接定位
    在catch中抛出异常，不会影响finally的执行。JVM会先执行finally，然后抛出异常。
##抛出异常
    当发生错误时，例如，用户输入了非法的字符，我们就可以抛出异常。
    如何抛出异常？参考Integer.parseInt()方法，抛出异常分两步：
    创建某个Exception的实例；
    用throw语句抛出
            void process2(String s) {
            if (s==null) {
                NullPointerException e = new NullPointerException();==>可缩写成throw new NullPointerException();
                throw e;
            }
        }
    如果一个方法捕获了某个异常后，又在catch子句中抛出新的异常，就相当于把抛出的异常类型“转换”了
    新的异常丢失了原始异常信息，我们已经看不到原始异常NullPointerException的信息了。
    为了能追踪到完整的异常栈，在构造异常的时候，把原始的Exception实例传进去，新的Exception就可以持有原始Exception信息。
    在catch中抛出异常，不会影响finally的执行。JVM会先执行finally，然后抛出异常。
##异常屏蔽
    finally抛出异常后，原来在catch中准备抛出的异常就“消失”了，因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常（Suppressed Exception）。
    在极少数的情况下，我们需要获知所有的异常。如何保存所有的异常信息？方法是先用origin变量保存原始异常，然后调用Throwable.addSuppressed()，把原始异常添加进来，最后在finally抛出：
    当catch和finally都抛出了异常时，虽然catch的异常被屏蔽了，但是，finally抛出的异常仍然包含了它
    通过Throwable.getSuppressed()可以获取所有的Suppressed Exception。
    绝大多数情况下，在finally中不要抛出异常。因此，我们通常不需要关心Suppressed Exception

##小结
    调用printStackTrace()可以打印异常的传播栈，对于调试非常有用；
    捕获异常并再次抛出新的异常时，应该持有原始异常信息；
    通常 不要在finally中抛出异常。如果在finally中抛出异常，应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。
 */
public class ThrowsStu {
    public static void main(String[] args) {

        //当process2()抛出NullPointerException后，被process1()捕获，然后抛出IllegalArgumentException()。

        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException();
        }
    }

    static void process2() {
        throw new NullPointerException();
    }

    /*
    java.lang.IllegalArgumentException
    at Main.process1(Main.java:15)
    at Main.main(Main.java:5)
      这说明新的异常丢失了原始异常信息，我们已经看不到原始异常NullPointerException的信息了。

        为了能追踪到完整的异常栈，在构造异常的时候，把原始的Exception实例传进去，新的Exception就可以持有原始Exception信息。

        修改后

        public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
    运行结果
            java.lang.IllegalArgumentException: java.lang.NullPointerException
            at Main.process1(Main.java:15)
            at Main.main(Main.java:5)
        Caused by: java.lang.NullPointerException
            at Main.process2(Main.java:20)
            at Main.process1(Main.java:13)
    注意到Caused by: Xxx，说明捕获的IllegalArgumentException并不是造成问题的根源，根源在于NullPointerException，是在Main.process2()方法抛出的。
    在代码中获取原始异常可以使用Throwable.getCause()方法。如果返回null，说明已经是“根异常”了。
    有了完整的异常栈的信息，我们才能快速定位并修复代码的问题。
     捕获到异常并再次抛出时，一定要留住原始异常，否则很难定位第一案发现场！
    如果我们在try或者catch语句块中抛出异常，finally语句是否会执行？例如
     */

    /*
    //
    Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
                e.addSuppressed(origin);
            }
            throw e;
        }
     */
}
